using System;
using System.Collections.Generic;
using System.Text;

namespace RapidHDL
{
    public class TestBenchVerilog
    {
        HardwareScript oHardwareScript;
        TopLevelComponent oTopLevel;
        RapidHardware RapidHardware;

        const string VERILOG_EXTENTION = ".v";
        System.IO.TextWriter oTextWriter;

        public string VerilogModuleName;
        public string VerilogText;

        public TestBenchVerilog(TopLevelComponent poTopLevel, HardwareScript poHardwareScript)
        {
            oTopLevel = poTopLevel;
            oHardwareScript = poHardwareScript;
            VerilogModuleName = oHardwareScript.Name + "_testbench";
            RapidHardware = poTopLevel.RapidHardware;
        }

        private void WriteVerilogHeader()
        {
            string sText;

            sText = "module " + VerilogModuleName + ";";
            this.WriteVerilogText(sText);

            WriteVerilogSkip();
        }

        private void WriteVerilogFooter()
        {
            WriteVerilogSkip();
            WriteVerilogText("endmodule");
        }

        private string SafeVerilogName(string psName)
        {
            string sSafe = psName.Replace(".", "_");
            return sSafe;
        }

        private string WireName(string psName)
        {
            string sWire = SafeVerilogName(psName);
            return sWire;
        }

        private string WireName(string psName, int piBit)
        {
            return WireName(psName) + "[" + piBit.ToString() + "]";
        }

        private string WireName(string psName,int piStart, int piEnd)
        {
            string sWire = SafeVerilogName(psName);

            if (piStart == piEnd )
            {
                return WireName(psName,piStart);   
            }
            return WireName(psName) + "[" + piStart.ToString() + ":" + piEnd.ToString() + "]";
        }

        private string WireName(string psName, NodeVector poNodeVector)
        {
            if (poNodeVector.Nodes.Count == 1)
                return WireName(psName);
            return WireName(psName, poNodeVector.Nodes.Count - 1, 0);
        }


        private void WriteVerilogInterface()
        {
            WriteVerilogSkip();
            WriteVerilogComment("Input Ports");
            foreach (NodeVector oNodeVector in oTopLevel.InputNodeVectors)
            {
                string sVerilog = "wire " + WireName("I_" + oNodeVector.Name, oNodeVector) + ";";
                WriteVerilogText(sVerilog);
            }

            WriteVerilogSkip();
            WriteVerilogComment("Output Ports");
            foreach (NodeVector oNodeVector in oTopLevel.OutputNodeVectors)
            {
                string sVerilog = "wire " + WireName("O_" + oNodeVector.Name, oNodeVector) + ";";
                WriteVerilogText(sVerilog);
            }
            
            WriteVerilogSkip();
            WriteVerilogComment("Signal Views");
            foreach (SignalView oSignalView in RapidHardware.SystemInterface.SignalViews.Values)
            {
                string sVerilog = "wire " + WireName(oSignalView.Name,oSignalView.DataView) + ";";
                WriteVerilogText(sVerilog);
            }

            WriteVerilogSkip();
            WriteVerilogComment("Signal Sources");
            foreach (SignalSource oSignalSource in RapidHardware.SystemInterface.SignalSources.Values)
            {
                string sVerilog = "";
                sVerilog = "wire " + WireName(oSignalSource.Name, oSignalSource.DataOut) + ";";
                WriteVerilogText(sVerilog);
            }

            WriteVerilogSkip();
        }


        public bool WriteToVerilog()
        {
            VerilogText = "";
            OpenVerilogFile();
            WriteVerilogHeader();
            WriteVerilogInterface();
            WriteVerilogComponentDeclaration();
            
            WriteVerilogFooter();
            CloseVerilogFile();
            return true;
        }

        public void OpenVerilogFile()
        {
            string sPath = RapidHardware.Settings.OutputPath + @"temp\";
            int iVersionCount = 1;

            sPath = RapidHardware.Settings.OutputPath + @"temp\";
            while (TextFile.FileExists(sPath, VerilogModuleName + VERILOG_EXTENTION))
            {
                iVersionCount++;
                VerilogModuleName = VerilogModuleName + iVersionCount.ToString();
            }

            oTextWriter = TextFile.OpenFileReWrite(sPath, VerilogModuleName + VERILOG_EXTENTION);
        }


        public void CloseVerilogFile()
        {
            string sTempName;
            int iVersionCount = 0;
            string sPath = RapidHardware.Settings.OutputPath + @"temp\";
            bool bSkip;

            //oTextWriter.Write(VerilogText);
            oTextWriter.Close();

            bSkip = false;

            sTempName = VerilogModuleName;

            while (TextFile.FileExists(sPath, sTempName + VERILOG_EXTENTION) & sTempName != VerilogModuleName)
            {
                if (TextFile.CheckFilesMatch(sPath, sTempName + VERILOG_EXTENTION, VerilogModuleName + VERILOG_EXTENTION, 2))
                {
                    //RenameVerilogFile(VerilogModuleName,sTempName)
                    TextFile.DeleteFile(sPath, VerilogModuleName + VERILOG_EXTENTION);
                    VerilogModuleName = sTempName;
                    bSkip = true;
                    continue;
                }
                iVersionCount++;
                sTempName = VerilogModuleName + iVersionCount.ToString();
            }
            if (!bSkip)
            {
                XilinxTools.AddXSTScript(VerilogModuleName + VERILOG_EXTENTION);
            }
        }

        public void WriteVerilogSkip()
        {
            WriteVerilogText("", 0);
        }

        public void WriteVerilogComment(string psHeading)
        {
            WriteVerilogHeadingComment(psHeading, 0);
        }

        public void WriteVerilogComment(string psHeading, int piIndent)
        {
            WriteVerilogText("// " + psHeading, piIndent);
        }

        public void WriteVerilogHeadingComment(string psHeading)
        {
            WriteVerilogHeadingComment(psHeading, 0);
        }

        public void WriteVerilogHeadingComment(string psHeading, int piIndent)
        {
            WriteVerilogSkip();
            WriteVerilogText("//" + Utility.StringRepeat("*", 5) + "  " + psHeading + "  " + Utility.StringRepeat("*", 5), piIndent);
            WriteVerilogSkip();
        }

        public void WriteVerilogText(string psText)
        {
            WriteVerilogText(psText, 0);
        }

        public void WriteVerilogText(string psText, int piIndent)
        {
            string sIndent = "";
            if (piIndent > 0)
                sIndent = Utility.StringRepeat(" ", 3 * piIndent);

            oTextWriter.WriteLine(sIndent + psText);
        }


        protected void WriteVerilogComponentDeclaration()
        {
            int iPortCount;
            int iTotalPorts;
            string sComma = "";
            string sPortMap = "";

            WriteVerilogText(oTopLevel.ComponentVerilog.VerilogModuleName + " " + oTopLevel.Name + " (");

            // input port headings
            iPortCount = 0;
            iTotalPorts = oTopLevel.InputNodeVectors.Count + oTopLevel.OutputNodeVectors.Count;
            foreach (NodeVector oNodeVector in oTopLevel.InputNodeVectors)
            {
                iPortCount++;
                if (iPortCount < iTotalPorts)
                    sComma = ",";
                else
                    sComma = "";

                sPortMap = SafeVerilogName("I_" + oNodeVector.Name);
                WriteVerilogText("." + oNodeVector.Name + "(" + sPortMap + ")" + sComma, 2);
            }

            // output port headings
            foreach (NodeVector oNodeVector in oTopLevel.OutputNodeVectors)
            {
                iPortCount++;
                if (iPortCount < iTotalPorts)
                    sComma = ",";
                else
                    sComma = "";

                sPortMap = SafeVerilogName("O_" + oNodeVector.Name);
                WriteVerilogText("." + oNodeVector.Name + "(" + sPortMap + ")" + sComma, 2);
            }
            WriteVerilogText(");", 1);
        }

        public string VerilogNodeVectorCriticalPortMap(NodeVector poNodeVector, Component poComponent)
        {
            // called at the level of the child component
            // locate parent component node vectors mapped to this child component's node vector

            Node oPortNode;
            Component oParentComponent;

            int iNodeStartPosition = 0;
            int iNodeEndPosition = 0;
            int iMaxNodeEndPosition = 0;

            int iPortCount = 0;

            string sNodeVector = "";
            string sPort;
            string sPortMap = "";


            int iNodeIndex = 1;

            oParentComponent = poNodeVector.ParentComponent;

            System.Collections.ArrayList oPortNodes = new System.Collections.ArrayList(poNodeVector.Size);

            // need to figure out which nodes in the net are getting connected in the port map
            foreach (Node oNode in poNodeVector.Nodes)
            {
                oPortNode = oNode.Net.SelectCriticalNode(poComponent);
                if (oPortNode == null)
                    System.Diagnostics.Debug.Fail("Verilog error in structure of " + oNode.HirarchicalName + " because no critical node found to connect port.");

                oPortNodes.Add(oPortNode);
            }

            iNodeIndex = 0;
            iPortCount = 0;
            foreach (Node oNode in oPortNodes)
            {
                if (iNodeIndex == 0)
                {
                    iNodeStartPosition = oNode.Position;
                    iNodeEndPosition = iNodeStartPosition;
                    iMaxNodeEndPosition = oNode.ParentNodeVector.Size - 1;
                    sNodeVector = oNode.ParentNodeVector.Name;
                }
                else
                {
                    if (iNodeEndPosition + 1 != oNode.Position | oNode.ParentNodeVector.Name != sNodeVector)
                    {
                        sPort = sNodeVector;
                        if (iNodeStartPosition != 0 | iNodeEndPosition != iMaxNodeEndPosition)
                        {
                            if (iNodeStartPosition == iNodeEndPosition)
                                sPort += "[" + iNodeStartPosition.ToString() + "]";
                            else
                                sPort += "[" + iNodeEndPosition.ToString() + ":" + iNodeStartPosition.ToString() + "]";

                        }
                        iNodeStartPosition = oNode.Position;
                        iNodeEndPosition = iNodeStartPosition;
                        iMaxNodeEndPosition = oNode.ParentNodeVector.Size - 1;
                        sNodeVector = oNode.ParentNodeVector.Name;

                        if (iPortCount == 0)
                            sPortMap = sPort + "}";
                        else
                            sPortMap = sPort + "," + sPortMap;

                        iPortCount++;
                    }
                    else
                        iNodeEndPosition++;
                }
                iNodeIndex++;
            }

            sPort = sNodeVector;
            if (iNodeStartPosition != 0 | iNodeEndPosition != iMaxNodeEndPosition)
            {
                if (iNodeStartPosition == iNodeEndPosition)
                    sPort += "[" + iNodeStartPosition.ToString() + "]";
                else
                    sPort += "[" + iNodeEndPosition.ToString() + ":" + iNodeStartPosition.ToString() + "]";

            }
            if (iPortCount == 0)
                sPortMap = sPort + "}";
            else
                sPortMap = sPort + "," + sPortMap;

            return "{" + sPortMap;
        }

        public void AssignSources()
        {

        }

        public void AssignSource(SignalSource poSource)
        {

        }

        public void AssignMatchingNodeVectors(string psNameA, NodeVector pnvA, string psNameB, NodeVector pnvB)
        {
            SortedDictionary<int, int> dicMatch = new SortedDictionary<int, int>();
            for (int iIdxA = 0; iIdxA < pnvA.Nodes.Count; iIdxA++)
            {
                Node ndA = (Node)pnvA.Nodes[iIdxA];
                for (int iIdxB = 0; iIdxB < pnvB.Nodes.Count; iIdxB++)
                {
                    Node ndB = (Node)pnvB.Nodes[iIdxB];
                    if (ndB.Net.ContainsNode(ndA))
                        dicMatch.Add(iIdxA, iIdxB);
                }
            }
            if (dicMatch.Count == 0)
                return;

            bool bSequential = false;
            int iLastNodeA = -1;
            int iLastNodeB = - 1;
            int iNodeAStart = 0;
            int iNodeBStart = 0;
            int iNodeAEnd = 0;
            int iNodeBEnd = 0;
            foreach (int iNodeA in dicMatch.Keys)
            {
                int iNodeB = dicMatch[iNodeA];
                if (iLastNodeA == -1)
                {
                    iLastNodeA = iNodeAStart = iNodeAEnd = iNodeA;
                    iLastNodeB = iNodeBStart = iNodeBEnd = iNodeB;
                }
                else
                {
                    if (iNodeA != iLastNodeA + 1)
                    {

                    }
                }
            }
            
        }

        private void WriteVerilogAssignment(string psNameA, string psNameB, int piStartA, int piEndA, int piStartB, int piEndB, NodeVector pnvA, NodeVector pnvB)
        {
            string sVerilog = "";
            if (pnvA.Nodes.Count == pnvB.Nodes.Count && piStartA == piStartB && System.Math.Abs(piEndA - piStartA) == pnvA.Nodes.Count)
            {
                sVerilog = "assign " + psNameA + " = " + psNameB + ";";
            }
        }

    }
}
